﻿using EnvDTE;
using EnvDTE80;
using Microsoft;
using Microsoft.VisualStudio.Shell;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace Exhort.Vsix
{
    public class Generate
    {
        private const string SolutionName = "Exhort";
        private const string DirectoryName = "netstandard2.0";

        Solution solution;
        private string path;
        private string solutionConfiguration;
        private List<EntityModel> list;

        public async System.Threading.Tasks.Task ExecuteGenerateAsync(AsyncPackage package)
        {
            await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

            DTE dte = (DTE)await package.GetServiceAsync(typeof(DTE));
            Assumes.Present(dte);
            solution = dte.Solution;
            path = solution.FullName.Replace($"{SolutionName}.sln", string.Empty);

            Project entityProject = GetProject(solution.Projects, $"{SolutionName}.Entity");
            if (entityProject == null) { return; }
            solutionConfiguration = solution.SolutionBuild.ActiveConfiguration.Name;
            solution.SolutionBuild.BuildProject(solutionConfiguration, entityProject.FullName, true);

            TypeInit();

            try
            {
                T4Configuration();
                T4DbSet();
                T4Affair();
                T4Service();
                T4ProxyRegistration();
                T4ServiceResolver();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            finally { }
        }

        private Project GetProject(Projects ps, string name)
        {
            ThreadHelper.ThrowIfNotOnUIThread();

            foreach (Project p in ps)
            {
                if (p.Kind.Equals(ProjectKinds.vsProjectKindSolutionFolder))
                {
                    var pp = GetProject(p.ProjectItems, name);
                    if (pp != null) { return pp; }
                }
                else { if (p.Name.Equals(name)) { return p; } }
            }

            return null;
        }

        private Project GetProject(ProjectItems ps, string name)
        {
            ThreadHelper.ThrowIfNotOnUIThread();

            foreach (ProjectItem pi in ps)
            {
                Project p = pi.SubProject;

                if (p.Kind.Equals(ProjectKinds.vsProjectKindSolutionFolder))
                {
                    var pp = GetProject(p.ProjectItems, name);
                    if (pp != null) { return pp; }
                }
                else { if (p.Name.Equals(name)) { return p; } }
            }

            return null;
        }

        private string GetAreaName(string typeNamespace)
        {
            return Regex.Match(typeNamespace, $"{SolutionName}.Entity.(?<area>.*)").Groups["area"].Value;
        }

        private void TypeInit()
        {
            string modelFile = $@"{path}{SolutionName}.Entity\bin\{solutionConfiguration}\{DirectoryName}\{SolutionName}.Entity.dll";

            byte[] fileData = File.ReadAllBytes(modelFile);

            var types = Assembly.Load(fileData).GetTypes().Where(m => !m.IsAbstract);

            list = new List<EntityModel>();

            foreach (Type type in types)
            {
                string areaName = GetAreaName(type.Namespace);
                string typeName = type.BaseType.Name.Replace("Entity", string.Empty);
                list.Add(new EntityModel(areaName, typeName, type.Name, type));
            }
        }

        private void T4Configuration()
        {
            foreach (var item in list)
            {
                string outPath = $@"{path}{SolutionName}.Configuration\{item.AreaName}\";
                T4.Configuration t4 = new T4.Configuration(SolutionName, item.AreaName, item.EntityName);
                ClassFileWrite(outPath, $"{item.EntityName}Configuration.cs", t4.TransformText(), false);
            }
        }

        private void T4DbSet()
        {
            foreach (var item in list)
            {
                string outPath = $@"{path}{SolutionName}.DbSet\{item.AreaName}\";
                T4.DbSet t4 = new T4.DbSet(SolutionName, item.AreaName, item.EntityName, item.TypeName);
                ClassFileWrite(outPath, $"{item.EntityName}DbSet.cs", t4.TransformText(), false);
            }
        }

        private void T4Affair()
        {
            string outPath = $@"{path}{SolutionName}.DbSet\";
            T4.Affair t4 = new T4.Affair(SolutionName, list);
            ClassFileWrite(outPath, "AffairGenerate.cs", t4.TransformText(), true);
        }

        private void T4Service()
        {
            foreach (var item in list)
            {
                string outPath = $@"{path}{SolutionName}.Service\{item.AreaName}\";
                T4.Service t4 = new T4.Service(SolutionName, item.AreaName, item.EntityName, item.TypeName);
                ClassFileWrite(outPath, $"{item.EntityName}Service.cs", t4.TransformText(), false);
            }
        }

        private void T4ProxyRegistration()
        {
            string outPath = $@"{path}{SolutionName}.Resolver\";
            T4.ProxyRegistration t4 = new T4.ProxyRegistration(SolutionName, list);
            ClassFileWrite(outPath, "ProxyRegistration.Generate.cs", t4.TransformText(), true);
        }

        private void T4ServiceResolver()
        {
            string outPath = $@"{path}{SolutionName}.Resolver\";
            T4.ServiceResolver t4 = new T4.ServiceResolver(SolutionName, list);
            ClassFileWrite(outPath, "ServiceResolver.Generate.cs", t4.TransformText(), true);
        }

        public static void ClassFileWrite(string path, string file, string text, bool cover)
        {
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }

            string filename = $"{path}{file}";

            if (!File.Exists(filename) || cover)
            {
                File.WriteAllText(filename, text);
            }
        }
    }
}
